#include "stdafx.h"
#include "CppUnitTest.h"

#include <vex/iterable/make_intrusive_range.h>
#include <vex.tests/mstest/log_message.h>
#include <vex.tests/mstest/crt_mem_check.h>
#include <vex.tests/mstest/tests.range/range_archetype.h>
#include <vex.tests/mstest/tests.range/forward_intrusive_range_call.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace tests {	namespace make_intrusive_range {

    namespace vc = vex::core;
    namespace vcc = vex::core::contract;
    namespace vi = vex::iterable;
    namespace vic = vex::iterable::contract;
    namespace vri = vex::iterable::implementation;

    TEST_CLASS(make_intrusive_range)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.range.make_intrusive_range")
            END_TEST_CLASS_ATTRIBUTE();

    public:
        template<class TRangeTraits>
        void result_of_make_intrusive_range_is_correctly_forwarded_through_call_hierarchy_impl(
            TRangeTraits, int p_exp_forward_calls)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            typedef TRangeTraits traits;

            forward_intrusive_range_call t_forward;

            // Conversions are not considered during type deduction. Therefore, t_forward.call
            // fails to compile if the passed object is of more derived type than the the 
            // type in the signature. Current remedy is to return contract types instead
            // of implementation types from factory functions.

            if (auto t_range = vi::make_intrusive_range(traits::range_type())) {
                typedef vi::contract::range<
                    typename traits::category, 
                    typename traits::reference
                > contract_t;
                Assert::AreEqual(p_exp_forward_calls, t_forward.call(
                    boost::static_pointer_cast<contract_t>(t_range)), wlog_message()
                    << L"failed for:" << std::endl << typeid(t_range).name() | as_string); 
            }
        }

        TEST_METHOD(result_of_make_intrusive_range_output_is_correctly_forwarded_through_call_hierarchy)
        {
            typedef vi::range_traits< range_archetype<vi::output_range_tag, int> > traits;
            typedef boost::intrusive_ptr< vic::range<vi::output_range_tag, int> > target_ip_t;
            typedef boost::intrusive_ptr< vic::range<> > trivial_target_ip_t;
            typedef decltype(vi::make_intrusive_range(traits::range_type())) source_ip_t;

            Assert::IsTrue(std::is_convertible<source_ip_t, target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, trivial_target_ip_t>::value);

            this->result_of_make_intrusive_range_is_correctly_forwarded_through_call_hierarchy_impl(
                traits(), 2);
        }

        TEST_METHOD(result_of_make_intrusive_range_one_pass_is_correctly_forwarded_through_call_hierarchy)

        {
            typedef vi::range_traits< range_archetype<vi::one_pass_range_tag, int&> > traits;
            typedef boost::intrusive_ptr< vic::range<vi::one_pass_tag, int&> > target_ip_t;
            typedef boost::intrusive_ptr< vic::range<> > trivial_target_ip_t;
            typedef decltype(vi::make_intrusive_range(traits::range_type())) source_ip_t;

            Assert::IsTrue(std::is_convertible<source_ip_t, target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, trivial_target_ip_t>::value);

            this->result_of_make_intrusive_range_is_correctly_forwarded_through_call_hierarchy_impl(
                traits(), 2);
        }

        TEST_METHOD(result_of_make_intrusive_range_forward_is_correctly_forwarded_through_call_hierarchy)
        {
            typedef vi::range_traits< range_archetype<vi::forward_range_tag, int&> > traits;
            typedef boost::intrusive_ptr< vic::range<vi::forward_tag, int&> > target_ip_t;
            typedef boost::intrusive_ptr< vic::range<vi::one_pass_tag, int&> > one_pass_target_ip_t;
            typedef boost::intrusive_ptr< vic::range<> > trivial_target_ip_t;
            typedef decltype(vi::make_intrusive_range(traits::range_type())) source_ip_t;

            Assert::IsTrue(std::is_convertible<source_ip_t, target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, trivial_target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, one_pass_target_ip_t>::value);

            this->result_of_make_intrusive_range_is_correctly_forwarded_through_call_hierarchy_impl(
                traits(), 3);
        }

        TEST_METHOD(result_of_make_intrusive_range_bidirectional_is_correctly_forwarded_through_call_hierarchy)
        {
            typedef vi::range_traits< range_archetype<vi::bidirectional_range_tag, int&> > traits;
            typedef boost::intrusive_ptr< vic::range<vi::bidirectional_tag, int&> > target_ip_t;
            typedef boost::intrusive_ptr< vic::range<vi::forward_tag, int&> > forward_target_ip_t;
            typedef boost::intrusive_ptr< vic::range<vi::one_pass_tag, int&> > one_pass_target_ip_t;
            typedef boost::intrusive_ptr< vic::range<> > trivial_target_ip_t;
            typedef decltype(vi::make_intrusive_range(traits::range_type())) source_ip_t;

            Assert::IsTrue(std::is_convertible<source_ip_t, target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, trivial_target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, one_pass_target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, forward_target_ip_t>::value);

            this->result_of_make_intrusive_range_is_correctly_forwarded_through_call_hierarchy_impl(
                traits(), 4);
        }

        TEST_METHOD(result_of_make_intrusive_range_random_access_is_correctly_forwarded_through_call_hierarchy)
        {
            typedef vi::range_traits< range_archetype<vi::random_access_range_tag, int&> > traits;
            typedef boost::intrusive_ptr< vic::range<vi::random_access_tag, int&> > target_ip_t;
            typedef boost::intrusive_ptr< vic::range<vi::bidirectional_tag, int&> > bidirectional_target_ip_t;
            typedef boost::intrusive_ptr< vic::range<vi::forward_tag, int&> > forward_target_ip_t;
            typedef boost::intrusive_ptr< vic::range<vi::one_pass_tag, int&> > one_pass_target_ip_t;
            typedef boost::intrusive_ptr< vic::range<> > trivial_target_ip_t;

            typedef decltype(vi::make_intrusive_range(traits::range_type())) source_ip_t;

            Assert::IsTrue(std::is_convertible<source_ip_t, target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, trivial_target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, one_pass_target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, forward_target_ip_t>::value);
            Assert::IsTrue(std::is_convertible<source_ip_t, bidirectional_target_ip_t>::value);

            this->result_of_make_intrusive_range_is_correctly_forwarded_through_call_hierarchy_impl(
                traits(), 5);
        }
    };
}}